package com.ayf.payment.game.api.channel.ayb.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ayf.payment.game.api.channel.ayb.response.AybUserTransferOrderQueryRes;
import com.ayf.payment.game.api.config.RequestConfig;
import com.ayf.payment.game.api.dto.platform.pay.EhbPayConfigParamVO;
import com.ayf.payment.game.api.exception.AuthorizeFailedException;
import com.ayf.payment.game.api.exception.InvalidResponseException;
import com.ayf.payment.game.api.httpclient.HttpRequestWrapper;
import com.ayf.payment.game.api.httpclient.MyRequestConfig;
import com.ayf.payment.game.api.httpclient.Request;
import com.ayf.payment.game.api.utils.DateUtils;
import com.ayf.payment.game.api.utils.RandomStrUtils;
import com.ayf.payment.game.api.vo.AybChannelConfigVO;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.swwx.charm.commons.lang.utils.LogPortal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.util.DigestUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Slf4j
public class AyUserTransferUtil implements Serializable {

    private static final String PLATFORM_CODE = System.getenv("GAME_AYFPAY_PLATFORM_CODE");


    private static final long serialVersionUID = 1L;
    private static final String HEADER_KEY_NONCE = "nonce";
    private static final String HEADER_KEY_TIMESTAMP = "timestamp";
    private static final String HEADER_KEY_AUTHORIZATION = "Authorization";
    public static final String RESPONSE_CODE = "code";
    public static final String RESPONSE_DATA = "data";
    private static String REQUEST_SUCCESS_FLAG = "reqSuccessFlag";


    public static <T> T request(String traceId, String url, String jsonReqData, AybChannelConfigVO aybChannelConfigVO, Integer socketTimeoutSecond, Class<T> clazz) throws IOException, InvalidResponseException {
        Map<String, String> result = doPost(traceId,url,jsonReqData,aybChannelConfigVO,socketTimeoutSecond);
        return dealWithResult(traceId,result,clazz);
    }

    public static <T> T request(String traceId, String url, String jsonReqData, AybChannelConfigVO aybChannelConfigVO, Class<T> clazz) throws IOException, InvalidResponseException {
        return request(traceId,url,jsonReqData,aybChannelConfigVO,0,clazz);
    }

    /**
     * 处理返回数据
     *
     * @param result
     * @param clazz
     * @param <T>
     * @return
     */
    private static <T> T dealWithResult(String traceId,Map<String, String> result,Class<T> clazz) throws SocketTimeoutException {
        log.info("处理返回数据result:{}", JSONObject.toJSONString(result));
        int resultCode = Integer.valueOf(result.get(RESPONSE_CODE)).intValue();
        String resultData = result.get(RESPONSE_DATA);
        JSONObject jo = JSONObject.parseObject(resultData);
        T t = null;
        boolean isSuccess = true;
        if (502 !=resultCode){
            if (504 == resultCode && resultData.contains("504 Gateway Time-out")){
                throw new SocketTimeoutException();
            }
            try {
                t = JSONObject.parseObject(jo.getString("data"),clazz);
            } catch (Exception e) {
                isSuccess = false;
            }
        } else {
            isSuccess = false;
        }
        try {
            if (t==null){
                t = clazz.newInstance();
            }
            Field f = clazz.getField(REQUEST_SUCCESS_FLAG);
            f.setAccessible(true);
            f.set(t,isSuccess);

            f = clazz.getSuperclass().getDeclaredField("code");
            f.setAccessible(true);
            f.set(t,jo.getString("code"));

            f = clazz.getSuperclass().getDeclaredField("msg");
            f.setAccessible(true);
            f.set(t,jo.getString("msg"));

        } catch (NoSuchFieldException e) {
            LogPortal.error("{} 发生NoSuchFieldException异常",e,traceId);
        } catch (IllegalAccessException e) {
            LogPortal.error("{} 发生IllegalAccessException异常",e,traceId);
        } catch (InstantiationException e) {
            LogPortal.error("{} 发生InstantiationException异常",e,traceId);
        }
        log.info("t====:{}", JSONObject.toJSONString(t));
        return t;
    }


    /**
     * POST请求
     * @author lch
     * @date 2021/12/10
     */
    private static Map<String, String> doPost(String traceId, String url, String jsonReqData, AybChannelConfigVO dto, int socketTimeoutSecond) throws IOException, InvalidResponseException, AuthorizeFailedException {
        HttpPost httpPost = new HttpPost(url);
        if(dto!=null){
            Map<String, String> headersMap = getCommonHeadersMap(dto.getMerchantId(), dto.getPublicKey());
            //添加header
            if (MapUtils.isNotEmpty(headersMap)) {
                for (Map.Entry<String, String> entry : headersMap.entrySet()) {
                    httpPost.setHeader(entry.getKey(), entry.getValue());
                }
            }
        }

        httpPost.setEntity(new ByteArrayEntity(jsonReqData.getBytes(Charset.forName(RequestConfig.CHARSET)), ContentType.APPLICATION_JSON));
        Map<String, String> result = null;
        long time_start = System.currentTimeMillis();
        LogPortal.info("{} 调用ayb-api请求开始 url:{} 请求参数:{}",traceId,url,jsonReqData);
        if (socketTimeoutSecond==0) {
            socketTimeoutSecond = 120;
        }
        org.apache.http.client.config.RequestConfig requestConfig = org.apache.http.client.config.RequestConfig.custom().
                setConnectTimeout(3 * 1000).
                setConnectionRequestTimeout(3 * 1000).
                setSocketTimeout(socketTimeoutSecond * 1000). //超时最大等待时间
                build();
        CloseableHttpClient httpClient;
        try {
            httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setSSLSocketFactory(new SSLConnectionSocketFactory(getSslContext())).build();
        } catch (NoSuchAlgorithmException e) {
            throw new IOException(e);
        } catch (KeyManagementException e) {
            throw new IOException(e);
        }
        try {
            CloseableHttpResponse response = httpClient.execute(httpPost);
            result = getResponseData(traceId, response, url, time_start, dto);
        }finally {
            httpPost.releaseConnection();
        }
        return result;
    }

    /**
     * POST请求
     *
     * @param url
     * @param jsonReqData
     * @return
     * @throws IOException
     * @throws InvalidResponseException
     */
    private static Map<String, String> doPostMd5(String traceId, String url, String jsonReqData, EhbPayConfigParamVO dto, int socketTimeoutSecond) throws IOException, InvalidResponseException, AuthorizeFailedException {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(new ByteArrayEntity(jsonReqData.getBytes(Charset.forName(RequestConfig.CHARSET)), ContentType.APPLICATION_JSON));
        Map<String, String> result = null;
        String sign = signData(httpPost, dto);
        httpPost.addHeader(RequestConfig.SIGN, sign);
        long time_start = System.currentTimeMillis();
        LogPortal.info("{} 调用merchant-api请求开始 url:{} 请求参数:{}",traceId,url,jsonReqData);
        if (socketTimeoutSecond==0) {
            socketTimeoutSecond = 120;
        }
        org.apache.http.client.config.RequestConfig requestConfig = org.apache.http.client.config.RequestConfig.custom().
                setConnectTimeout(3 * 1000).
                setConnectionRequestTimeout(3 * 1000).
                setSocketTimeout(socketTimeoutSecond * 1000). //超时最大等待时间
                build();
        CloseableHttpClient httpClient;
        try {
            httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setSSLSocketFactory(new SSLConnectionSocketFactory(getSslContext())).build();
        } catch (NoSuchAlgorithmException e) {
            throw new IOException(e);
        } catch (KeyManagementException e) {
            throw new IOException(e);
        }
        try {
            CloseableHttpResponse response = httpClient.execute(httpPost);
            result = verifyData(traceId, response,url,time_start,dto);
        }finally {
            httpPost.releaseConnection();
        }
        return result;
    }

    /**
     * POST请求 默认超时等待2分钟
     * @param traceId
     * @param url
     * @param jsonReqData
     * @param dto
     * @return
     * @throws IOException
     * @throws InvalidResponseException
     * @throws AuthorizeFailedException
     */
    private static Map<String, String> doPost(String traceId, String url, String jsonReqData, AybChannelConfigVO dto) throws IOException, InvalidResponseException, AuthorizeFailedException {
        return doPost(traceId,url,jsonReqData,dto,0);
    }


    /**
     * 取出返回结果
     * @author lch
     * @date 2021/12/10
     */
    private static Map<String, String> getResponseData(String traceId, CloseableHttpResponse response, String url, long time_start, AybChannelConfigVO dto) throws IOException, InvalidResponseException {
        Map<String, String> result = null;
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            try {
                int responseCode = response.getStatusLine().getStatusCode();
                String resData = EntityUtils.toString(entity, Charset.forName(RequestConfig.CHARSET));
                LogPortal.info("{} 调用ayb-api请求响应 url:{}，耗时:{}ms，responseCode={}，resData={}",traceId,url,(System.currentTimeMillis()-time_start),responseCode,resData);
//                if (responseCode ==200) {
//                    byte[] data = resData.getBytes(RequestConfig.CHARSET);
//                }
                result = new HashMap<>();
                result.put(RESPONSE_CODE, String.valueOf(responseCode));
                result.put(RESPONSE_DATA, resData);
            } finally {
                EntityUtils.consumeQuietly(entity);
            }
        }
        return result;
    }


    /**
     * 对返回结果进行验签。
     * 验签成功:返回数据
     * 验签失败:抛出异常
     *
     * @param response
     * @return
     * @throws IOException
     * @throws InvalidResponseException
     */
    private static Map<String, String> verifyData(String traceId, CloseableHttpResponse response, String url, long time_start, EhbPayConfigParamVO dto) throws IOException, InvalidResponseException {
        Map<String, String> result = null;
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            try {
                int responseCode = response.getStatusLine().getStatusCode();
                String resData = EntityUtils.toString(entity, Charset.forName(RequestConfig.CHARSET));
                LogPortal.info("{} 调用merchant-api请求响应 url:{}，耗时:{}ms，responseCode={}，resData={}",traceId,url,(System.currentTimeMillis()-time_start),responseCode,resData);
                if (responseCode ==200) {
                    String nonce = response.getFirstHeader(HEADER_KEY_NONCE) != null ? response.getFirstHeader(HEADER_KEY_NONCE).getValue() : "";
                    String timestamp = response.getFirstHeader(HEADER_KEY_TIMESTAMP) != null ? response.getFirstHeader(HEADER_KEY_TIMESTAMP).getValue() : "";
                    String secretKey = response.getFirstHeader(HEADER_KEY_AUTHORIZATION) != null ? response.getFirstHeader(HEADER_KEY_AUTHORIZATION).getValue() : "";
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    out.write(nonce.getBytes(RequestConfig.CHARSET));
                    out.write('\n');//header
                    out.write(timestamp.getBytes(RequestConfig.CHARSET));
                    out.write('\n');//header
                    out.write(secretKey.getBytes(RequestConfig.CHARSET));
                    out.write('\n');//header
                    byte[] data = resData.getBytes(RequestConfig.CHARSET);
                    out.write(data);//body
                    out.close();
                    String toVerifyData = out.toString();
                    String sign = response.getFirstHeader(RequestConfig.SIGN) != null ? response.getFirstHeader(RequestConfig.SIGN).getValue() : "";
                    boolean flag = RSA.verify(toVerifyData,sign,dto.getChannelPubKey());
                    if (!flag) {
                        throw new InvalidResponseException("Invalid Response.[Response Data And Sign Verify Failure.]");
                    }
                    if (!dto.getChannelSecretKey().equals(secretKey)) {
                        throw new InvalidResponseException("Invalid Response.[Secret Key Is Invalid.]");
                    }
                }
                result = new HashMap<>();
                result.put(RESPONSE_CODE, String.valueOf(responseCode));
                result.put(RESPONSE_DATA, resData);
            } finally {
                EntityUtils.consumeQuietly(entity);
            }
        }
        return result;
    }

    /**
     * 设置header
     *
     * @param request
     */
    private static void setCustomHeaders(HttpRequestBase request, EhbPayConfigParamVO dto) {
        request.addHeader("Content-Type", "application/json; charset=" + RequestConfig.CHARSET);
        request.addHeader(HEADER_KEY_AUTHORIZATION, dto.getChannelSecretKey());
        String timestamp = String.valueOf(System.currentTimeMillis());
        request.addHeader(HEADER_KEY_TIMESTAMP, timestamp);
        request.addHeader(HEADER_KEY_NONCE, UUID.randomUUID().toString().replaceAll("-", ""));
        String[] propertyNames = {"os.name", "os.version", "os.arch", "java.version", "java.vendor", "java.vm.version", "java.vm.vendor"};
        Map<String, String> propertyMap = new HashMap<>(10);
        for (String propertyName : propertyNames) {
            propertyMap.put(propertyName, System.getProperty(propertyName));
        }
        propertyMap.put("lang", "Java");
        propertyMap.put("publisher", "HttpUtils");
        propertyMap.put("sdk-version", RequestConfig.SDK_VERSION);
        request.addHeader("X-HttpUtil-Client-User-Agent", JSON.toJSONString(propertyMap));
    }

    /**
     * 签名方法
     *
     * @param httpRequest
     * @return
     * @throws IOException
     */
    private static String signData(HttpRequestBase httpRequest, EhbPayConfigParamVO dto) throws IOException {
        Request<HttpRequest> request = new HttpRequestWrapper(httpRequest);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write(request.getMethod().toLowerCase().getBytes(RequestConfig.CHARSET));
        out.write('\n');//method
        String queryString = StringUtils.isBlank(request.getRequestQueryString()) ? "" : request.getRequestQueryString();
        out.write(queryString.getBytes(RequestConfig.CHARSET));
        out.write('\n');//query string
        out.write(request.getHeaderValue(HEADER_KEY_NONCE).getBytes(RequestConfig.CHARSET));
        out.write('\n');//header
        out.write(request.getHeaderValue(HEADER_KEY_TIMESTAMP).getBytes(RequestConfig.CHARSET));
        out.write('\n');//header
        out.write(request.getHeaderValue(HEADER_KEY_AUTHORIZATION).getBytes(RequestConfig.CHARSET));
        out.write('\n');//header
        byte[] data = IOUtils.toByteArray(request.getRequestBody());
        out.write(data);//body
        out.close();
        String toSignString = out.toString();
        String url = request.getRequestUriPath();
        LogPortal.debug("请求URL:{} 待签名字符串:{}", url, toSignString);
        String sign = RSA.sign(toSignString, dto.getMerchantPriKey());
        LogPortal.debug("请求URL:{} 生成的签名:{}", url, sign);
        return sign;
    }


    private static SSLContext getSslContext() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        }}, new SecureRandom());
        return sslContext;
    }

    /**
     * GET请求
     *
     * @param url
     * @param <T>
     * @return
     */
    public static <T> T doGet(String url) {
        //1、获取HttpClient对象
        CloseableHttpClient client = HttpClients.createDefault();
        //2、创建连接的方式
        HttpGet get = new HttpGet(url);
        //3、设置请求方式连接的参数
        get.setConfig(MyRequestConfig.getRequestConfig());
        CloseableHttpResponse response = null;
        try {
            //4、执行请求,获取服务器返回的结果
            response = client.execute(get);
            String str = EntityUtils.toString(response.getEntity(), "utf-8");
            ObjectMapper om = new ObjectMapper();
            T t = om.readValue(str, new TypeReference<T>() {
            });
            return t;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭连接
                response.close();
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }


    public static Map<String, String> getCommonHeadersMap(Integer merchantId, String publicKey) {
        String timestamp = DateUtils.dateToString(DateUtils.getNowDate(), DateUtils.formatPattern_full_01);
        String nonce = RandomStrUtils.createRandom(false, 8);
        String str = publicKey+nonce+timestamp;
        //加密后string
        String md5Str = DigestUtils.md5DigestAsHex(str.getBytes());

        Map<String, String> headersMap = Maps.newTreeMap();
        headersMap.put("content-type","application/json");
        headersMap.put("platformCode", PLATFORM_CODE);
        headersMap.put("merchantId",merchantId.toString());
        headersMap.put("timestamp",timestamp);
        headersMap.put("nonce", nonce);
        headersMap.put("signature",md5Str);

        return headersMap;
    }

    public static void main(String[] args) throws Exception {
//        String timestamp = "638124";
//        String nonce = "jifawoefh32ifhqw";
//        String publicKey = "df4b98f27cced339237a9e8261548486";
//        String str = publicKey+nonce+timestamp;
//        //加密后string
//        String md5Str = DigestUtils.md5DigestAsHex(str.getBytes());
//        System.out.println(md5Str);



        Class<AybUserTransferOrderQueryRes> clazz = AybUserTransferOrderQueryRes.class;

        AybUserTransferOrderQueryRes t = clazz.newInstance();

        Field f = clazz.getSuperclass().getDeclaredField("code");
        f.setAccessible(true);
        f.set(t, "1");

        f = clazz.getSuperclass().getField("msg");
        f.setAccessible(true);
        f.set(t,"xxx");
    }
}
